/* ****************************************************************************
 * Copyright (c) TCC - 2009 IME - USP. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. 
 * A copy of the license can be found in http://msdn.microsoft.com/en-us/library/dd228915.aspx. 
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the 
 * Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 * ***************************************************************************/

namespace SoundLibrary
{
    using System;
    using System.IO;

    [Serializable]
    internal class CircularByteBuffer
    {
        private byte[] dataArray = null;
        private int length = 1;
        private int index = 0;
        private int numValid = 0;

        #region Constructors
        public CircularByteBuffer(int size)
        {
            this.dataArray = new byte[size];
            this.length = size;
        }

        public CircularByteBuffer(CircularByteBuffer cdb)
        {
            lock (cdb)
            {
                this.length = cdb.length;
                this.numValid = cdb.numValid;
                this.index = cdb.index;
                this.dataArray = new byte[this.length];
                for (int c = 0; c < this.length; c++)
                {
                    this.dataArray[c] = cdb.dataArray[c];
                }
            }
        }
        #endregion

        #region Properties
        public int NumValid
        {
            get
            {
                return this.numValid;
            }

            set
            {
                if (value > this.numValid)
                {
                    throw new Exception("Can't set NumValid to " + value + " which is greater than the current numValid value of " + this.numValid);
                }

                this.numValid = value;
            }
        }

        public int BufferSize
        {
            get
            {
                return this.length;
            }

            set
            {
                byte[] newDataArray = new byte[value];

                int minLength = (this.length > value) ? value : this.length;
                for (int i = 0; i < minLength; i++)
                {
                    newDataArray[i] = this.InternalGet(i - this.length + 1);
                }

                this.dataArray = newDataArray;
                this.index = minLength - 1;
                this.length = value;
            }
        }

        public byte this[int index]
        {
            get
            {
                return this.InternalGet(-1 - index);
            }

            set
            {
                this.InternalSet(-1 - index, value);
            }
        }
        #endregion

        #region Public Methods
        public CircularByteBuffer Copy()
        {
            return new CircularByteBuffer(this);
        }

        public void Reset()
        {
            this.index = 0;
            this.numValid = 0;
        }

        public byte Push(byte newValue)
        {
            byte ret;
            lock (this)
            {
                ret = this.InternalGet(this.length);
                this.dataArray[this.index] = newValue;
                this.numValid++;
                if (this.numValid > this.length)
                {
                    this.numValid = this.length;
                }

                this.index++;
                this.index %= this.length;
            }

            return ret;
        }

        public byte Pop()
        {
            lock (this)
            {
                if (this.numValid == 0)
                {
                    throw new Exception("Can't pop off an empty CircularByteBuffer");
                }

                this.numValid--;
                return this[this.numValid];
            }
        }

        public byte Peek()
        {
            lock (this)
            {
                return this.InternalGet(this.length);
            }
        }

        public byte[] GetRange(int str, int stp)
        {
            byte[] outByte = new byte[str - stp + 1];

            for (int i = str, j = 0; i >= stp; i--, j++)
            {
                outByte[j] = this[i];
            }

            return outByte;
        }

        public override string ToString()
        {
            string ret = String.Empty;
            for (int i = 0; i < this.dataArray.Length; i++)
            {
                ret += this.dataArray[i] + " ";
            }

            ret += "\n index = " + this.index + " numValid = " + this.NumValid;
            return ret;
        }
        #endregion

        #region Private Methods
        private byte InternalGet(int offset)
        {
            int ind = this.index + offset;

            // Do thin modulo (should just drop through)
            for (; ind >= this.length; ind -= this.length)
            {
            }

            for (; ind < 0; ind += this.length)
            {
            }

            // Set value
            return this.dataArray[ind];
        }

        private void InternalSet(int offset, byte valueToSet)
        {
            int ind = this.index + offset;
            for (; ind > this.length; ind -= this.length)
            {
            }

            for (; ind < 0; ind += this.length)
            {
            }

            this.dataArray[ind] = valueToSet;
        }
        #endregion
    }

    internal class BackStream
    {
        private Stream stream;
        private int backBufferSize;
        private int numForwardBytesInBuffer = 0;
        private byte[] temp;
        private CircularByteBuffer circularByteBuffer;

        #region Constructors
        public BackStream(Stream s, int backBufferSize)
        {
            this.stream = s;
            this.backBufferSize = backBufferSize;
            this.temp = new byte[backBufferSize];
            this.circularByteBuffer = new CircularByteBuffer(backBufferSize);
        }
        #endregion

        #region Public Methods
        public int Read(sbyte[] toRead, int offset, int length)
        {
            int currentByte = 0;
            bool canReadStream = true;
            while (currentByte < length && canReadStream)
            {
                if (this.numForwardBytesInBuffer > 0)
                {
                    this.numForwardBytesInBuffer--;
                    toRead[offset + currentByte] = (sbyte)this.circularByteBuffer[this.numForwardBytesInBuffer];
                    currentByte++;
                }
                else
                {
                    int newBytes = length - currentByte;
                    int numRead = this.stream.Read(this.temp, 0, newBytes);
                    canReadStream = numRead >= newBytes;
                    for (int i = 0; i < numRead; i++)
                    {
                        this.circularByteBuffer.Push(this.temp[i]);
                        toRead[offset + currentByte + i] = (sbyte)this.temp[i];
                    }

                    currentByte += numRead;
                }
            }

            return currentByte;
        }

        public void UnRead(int length)
        {
            this.numForwardBytesInBuffer += length;
            if (this.numForwardBytesInBuffer > this.backBufferSize)
            {
                Console.WriteLine("YOUR BACKSTREAM IS FISTED!");
            }
        }

        public void Close()
        {
            this.stream.Close();
        }
        #endregion
    }
}
